package com.cdqidi.core.weixinmanager.service.wx;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cdqidi.core.weixinmanager.domain.WxDTO;
import com.cdqidi.core.weixinmanager.model.OrgWxConfig;
import com.cdqidi.core.weixinmanager.model.UserInfo;
import com.cdqidi.core.weixinmanager.service.OrgWxConfigService;
import com.cdqidi.core.weixinmanager.service.UserInfoService;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisTemplateUtil;
import com.jfinal.weixin.sdk.api.*;
import com.jfinal.wxaapp.api.WxaUserApi;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.concurrent.TimeUnit;

/**
 * 〈一句话功能简述〉<br>
 *
 * @author sjk
 * @since 1.0.0
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class WxApiService {
    private final OrgWxConfigService orgWxConfigService;
    private final UserInfoService wxUserInfoService;
    private final WxDTO wxDto;
    private static final String PREFIX_ID = "is_update_{0}";
    private static final String OPEN_ID = "openid";
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 根据小程code小程序openid，并存储当前小程序登录人信息
     *
     * @param jsCode 前端小程序传入
     * @return openid
     */
    public String getWxaOpenId(String jsCode) {
        final OrgWxConfig wxConfig = orgWxConfigService.getWxConfig();
        if (null == wxConfig) {
            throw new ApiException("当前公众号未在系统里配置");
        }
        final ApiResult result = WxaUserApi.getSessionKey(jsCode);
        if (result.isSucceed()) {
            String openid = result.get(OPEN_ID);
            saveWxaUserInfo(result, wxConfig.getAppId(), "sf1");
            return openid;
        }
        throw new ApiException("获取openID失败: " + result.getErrorMsg());
    }

    /**
     * 保存小程序用户基本信息
     *
     * @param result 微信返回
     * @param appId  公众号id
     * @param isSub  关注/取消关注
     */
    public void saveWxaUserInfo(ApiResult result, String appId, String isSub) {
        String openid = result.getStr(OPEN_ID);
        if (!isSaveWxUserInfo(openid)) {
            return;
        }
        UserInfo model = new UserInfo();
        model.setOpenid(openid);
        model.setAppId(appId);
        model.setUnionId(result.getStr("unionid"));
        model.setSub(isSub);
        model.setAddTime(LocalDateTime.now());
        model.setUpdateTime(model.getAddTime());
        wxUserInfoService.saveOrUpdate(model);
        updateWxUserInfoCacheState(openid);
    }

    /**
     * 更新微信用户当前关注状态
     *
     * @return 关注信息
     */
    public UserInfo updateWxUserInfo(ApiResult result, Long createTime, String isSub) {
        String openid = result.getStr(OPEN_ID);
        UserInfo model = wxUserInfoService.getById(openid);
        if (null != model) {
            log.info("取消关注，subscribe_time： {}", createTime);
            if (null != createTime) {
                Long subscribeTime = model.getSubscribeTime();
                if (null != subscribeTime) {
                    if (createTime < subscribeTime) {
                        log.error("获取记录时间小于数据库存储时间，json： {}", result.getJson());
                    }
                    model.setUpdateTime(Instant.ofEpochSecond(createTime).atOffset(ZoneOffset.of("+08:00")).toLocalDateTime());
                }
            }
            model.setSub(isSub);
            wxUserInfoService.saveOrUpdate(model);
            updateWxUserInfoCacheState(model.getOpenid());
        }
        return model;
    }

    /**
     * 保存微信用户信息
     *
     * @param result 微信端返回
     * @param appId  公众号id
     * @return 用户信息
     */
    public UserInfo saveWxUserInfo(ApiResult result, String appId, String isSub) {
        String openid = result.getStr(OPEN_ID);
        final Long subscribeTime = result.getLong("subscribe_time");
        UserInfo model = wxUserInfoService.getById(openid);
        if (null == model) {
            model = new UserInfo();
        } else {
            Long subscribeTime1 = model.getSubscribeTime();
            if (null != subscribeTime && null != subscribeTime1) {
                if (subscribeTime < subscribeTime1) {
                    log.error("获取记录时间小于数据库存储时间，json: {}", result.getJson());
                }
            }
        }
        model.setCity(result.getStr("city"));
        model.setCountry(result.getStr("country"));
        final Integer groupId = result.getInt("groupid");
        if (null != groupId) {
            model.setGroupId(groupId + "");
        }
        model.setHeadImgUrl(result.getStr("headimgurl"));
        model.setLanguage(result.getStr("language"));
        model.setNickName(result.getStr("nickname"));
        model.setOpenid(result.getStr(OPEN_ID));
        model.setProvince(result.getStr("province"));
        int sex = result.getInt("sex");
        String sexStr;
        switch (sex) {
            case 1:
                sexStr = "sex1";
                break;
            case 2:
                sexStr = "sex2";
                break;
            default:
                sexStr = "sex0";
                break;
        }
        model.setSex(sexStr);
        model.setStatus(0);
        if (null != isSub) {
            model.setSub(isSub);
        }
        final Integer subscribe = result.getInt("subscribe");
        model.setSubscribe(subscribe);
        if (null != subscribeTime) {
            model.setSubscribeTime(subscribeTime);
            model.setUpdateTime(Instant.ofEpochSecond(subscribeTime).atOffset(ZoneOffset.of("+08:00")).toLocalDateTime());
        }
        model.setUnionId(result.getStr("unionid"));
        model.setAppId(appId);
        model.setOpenid(openid);
        model.setAddTime(LocalDateTime.now());
        wxUserInfoService.saveOrUpdate(model);
        return model;
    }

    /**
     * 如果需要立即更新微信用户信息到数据库，可以强制删除缓存
     *
     * @param openId 微信openid
     */
    public void clearWxUserInfoCacheState(String openId) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, openId);
        stringRedisTemplate.delete(key);
    }

    /**
     * 判断是否需要保存微信用户信息到数据库
     *
     * @param openId 微信openid
     * @return true or false
     */
    private boolean isSaveWxUserInfo(String openId) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, openId);
        String s = stringRedisTemplate.opsForValue().get(key);
        return !StringUtils.hasLength(s);
    }

    /**
     * 修改缓存状态，存储一个openid，时间为24小时，每隔24小时才存储一次微信用户信息
     *
     * @param openId 微信openid
     */
    private void updateWxUserInfoCacheState(String openId) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, openId);
        stringRedisTemplate.opsForValue().set(key, openId, 1, TimeUnit.DAYS);
    }

    public void redirectAuthorizeURL(HttpServletResponse response, String redirectUri, String path, boolean scope) {
        String state = IdWorker.getIdStr();
        stringRedisTemplate.opsForValue().set(RedisTemplateUtil.buildKey(state, true), state, 60, TimeUnit.SECONDS);
        try {
            StringBuilder param = new StringBuilder();
            param.append("?appId=");
            param.append(ApiConfigKit.getAppId());
            String redirectUriStr = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8);
            if (log.isDebugEnabled()) {
                log.debug("_redirectUri: {}", redirectUriStr);
                log.debug("scope: {}", scope);
            }
            int i = redirectUriStr.indexOf("?");
            String str;
            if (i == -1) {
                str = redirectUriStr;
            } else {
                str = redirectUriStr.substring(0, i);
                param.append("&");
                param.append(redirectUriStr.substring(i + 1));
            }
            if (null != path) {
                if (log.isDebugEnabled()) {
                    log.debug("param: {}", param);
                }
                param.append("&path=");
                param.append(path);
            }
            String paramId = IdWorker.getIdStr();
            String key = RedisTemplateUtil.getRedisCacheKey(paramId, true);
            if (log.isDebugEnabled()) {
                log.debug("url: {}", str);
                log.debug("param: {}", param);
            }
//            String redirect = wxDto.getWxDomain() + "/wxmanager/wxApi/getOpenid?id=" + paramId + "&appId=" + ApiConfigKit.getAppId();
            String redirect = str + "?id=" + paramId;
            if (log.isDebugEnabled()) {
                log.debug("redirect: {}", redirect);
                log.debug("str+param: {}", str + param);
            }
            stringRedisTemplate.opsForValue().set(key, str + param, 60, TimeUnit.SECONDS);
            final String authorizeUrl = SnsAccessTokenApi.getAuthorizeURL(ApiConfigKit.getAppId(), URLEncoder.encode(redirect, StandardCharsets.UTF_8), state, scope);
            response.sendRedirect(authorizeUrl);
        } catch (IOException e) {
            throw new ApiException(e);
        }
    }

    public void getOpenid(HttpServletRequest request, HttpServletResponse response) {
        String state = request.getParameter("state");
        final String code = request.getParameter("code");
        if (StringUtils.hasLength(state)) {
            String key = RedisTemplateUtil.buildKey(state, true);
            final String s = stringRedisTemplate.opsForValue().get(key);
            if (StringUtils.hasLength(s) && s.equals(state)) {
                stringRedisTemplate.delete(key);
                final String id = request.getParameter("id");
                if (StringUtils.hasLength(id)) {
                    String idKey = RedisTemplateUtil.buildKey(id, true);
                    String redirectUrl = stringRedisTemplate.opsForValue().get(idKey);
                    if (log.isDebugEnabled()) {
                        log.debug("redirectUrl: {}", redirectUrl);
                    }
                    try {
                        assert redirectUrl != null;
                        int i = redirectUrl.indexOf("?");
                        StringBuilder url = new StringBuilder(redirectUrl);
                        final String params = redirectUrl.substring(i + 1);
                        final String[] ps = params.split("&");
                        String appId = getAppId(ps);
                        SnsAccessToken snsAccessToken = getSnsAccessToken(code, appId);
                        if (null != snsAccessToken) {
                            if (StringUtils.hasLength(snsAccessToken.getOpenid())) {
                                url.append("&openid=");
                                url.append(URLEncoder.encode(snsAccessToken.getOpenid(), StandardCharsets.UTF_8));
                            }
                            if (StringUtils.hasLength(snsAccessToken.getUnionid())) {
                                url.append("&unionId=");
                                url.append(URLEncoder.encode(snsAccessToken.getUnionid(), StandardCharsets.UTF_8));
                            }
                        }
                        if (log.isDebugEnabled()) {
                            log.debug("redirectUrl: {}", url);
                        }
                        response.sendRedirect(url.toString());
                    } catch (IOException e) {
                        throw new ApiException(e);
                    }
                }

            }
        }
    }

    public String getAppId(String[] ps) {
        String appId;
        for (String s : ps) {
            final String[] strings = s.split("=");
            if ("appId".equals(strings[0])) {
                appId = strings[1];
                return appId;
            }
        }
        return "";
    }

    public SnsAccessToken getSnsAccessToken(String code, String appId) {
        if (StringUtils.hasLength(appId)) {
            ApiConfigKit.setThreadLocalAppId(appId);
            SnsAccessToken sns = SnsAccessTokenApi.getSnsAccessToken(appId, ApiConfigKit.getApiConfig().getAppSecret(), code);
            if (sns != null) {
                final ApiResult result = SnsApi.getUserInfo(sns.getAccessToken(), sns.getOpenid());
                final OrgWxConfig wxConfig = orgWxConfigService.getById(ApiConfigKit.getAppId());
                if (null != wxConfig) {
                    if (log.isDebugEnabled()) {
                        log.debug("微信获得openid: {}", result.getJson());
                    }
//                    saveWxUserInfo(result, wxConfig.getAppId(), null);
                }
                return sns;
            }
        }
        return null;
    }
}